﻿using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace YaTools.Yaml
{
    /// <summary>
    /// YAML-friendly version of the System.Runtime.Serialization.ObjectManager
    /// </summary>
    /// <remarks>
    /// Anchors in YAML differ from ObjectManager Object IDs in two significant
    /// ways:  1) Anchors are strings.   2) Anchors may be recycled.   This 
    /// class provides a translation layer during deserialization.
    /// </remarks>
    internal sealed class AnchorObjectManager : ObjectManager
    {
        // Problem: It looks like the ObjectManager only knows how to do fixups on:
        // 1) Object Member properties - RecordFixup() by MemberInfo
        // 2) Object Member properties - RecordDelayedFixup() name: string
        // 3) Array elements - Record ArrayElementFixup
        //
        // It can't do fixups to other collection types.  Probably OK for binary 
        // serialization since the collections have arrays at their hearts, but 
        // not for a public-oriented serializer like ours...

        private readonly Dictionary<string, long> _anchors = new Dictionary<string, long>();
        private long _nextID = 1;

        public AnchorObjectManager(
            ISurrogateSelector selector,
            StreamingContext context)
            : base(selector, context)
        {
        }

        [ContractInvariantMethod]
        private void ObjectInvariants()
        {
            Contract.Invariant(_anchors != null);
            Contract.Invariant(_nextID > 0);
        }

        private long NewID(string anchor)
        {
            Contract.Requires(anchor != null);
            Contract.Requires(anchor.Length > 0);

            long result = _nextID++;
            _anchors[anchor] = result;
            return result;
        }

        private long GetID(string anchor)
        {
            long result;
            if (!_anchors.TryGetValue(anchor, out result)) result = 0;
            return result;
        }

        public override object GetObject(long objectID)
        {
            if (objectID == 0) return null;
            return base.GetObject(objectID);
        }

        public object GetObject(string anchor)
        {
            return GetObject(GetID(anchor));
        }


        [SecurityPermission(SecurityAction.LinkDemand,
            Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void RegisterObject(object obj, string anchor)
        {
            Contract.Requires(!string.IsNullOrEmpty(anchor));

            RegisterObject(obj, NewID(anchor));
        }

        [SecurityPermission(SecurityAction.LinkDemand,
            Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void RegisterObject(object obj, string anchor,
                                   SerializationInfo info)
        {
            Contract.Requires(!string.IsNullOrEmpty(anchor));

            RegisterObject(obj, NewID(anchor), info);
        }

        [SecurityPermission(SecurityAction.LinkDemand,
            Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void RegisterObject(object obj, string anchor,
                                   SerializationInfo info, long idOfContainingObj,
                                   MemberInfo member)
        {
            Contract.Requires(!string.IsNullOrEmpty(anchor));

            RegisterObject(obj, NewID(anchor), info, idOfContainingObj, member);
        }

        [SecurityPermission(SecurityAction.LinkDemand,
            Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void RegisterObject(object obj, string anchor, SerializationInfo info,
                                   long idOfContainingObj, MemberInfo member, int[] arrayIndex)
        {
            Contract.Requires(!string.IsNullOrEmpty(anchor));

            RegisterObject(obj, NewID(anchor), info, idOfContainingObj, member, arrayIndex);
        }
    }
}